<?
// vim: ts=2:sw=2:nu:fdc=4
/**
  * cfiletree class file
  * 
  * @author        Ing. Jozef Sakalos <jsakalos@aariadne.com>
  * @copyright     (c) 2007 by Ing. Jozef Sakalos
  * @date          07. July 2007
  * @version       $Id: cfiletree.php 55 2007-07-20 19:50:39Z jozo $
  * @filesource
  */

/**
  * Server side class for handling Ext.ux.FileTree requests
  *
  * Handled commands (cmd):
  * - get Read files/dirs at the specified path and returns nodes
	* - upload Handles file upload
  *
  * @author        Ing. Jozef Sakalos <jsakalos@aariadne.com>
  * @copyright     (c) 2007 by Ing. Jozef Sakalos
  * @version       0.1
  * @date          07. July 2007
  */
class cfiletree {

	// protected properties
	// {{{
	/**
	  * $o: return object. It's json encoded and returned to client
	  *
	  * @date    07. July 2007
	  * @access  protected
	  * @var     object
	  */
	var $o;
	// }}}
	// {{{
	/**
	  * $clientArgs: Client arguments ($_POST or $_GET)
	  *
	  * @date    07. July 2007
	  * @access  protected
	  * @var     array
	  */
	var $clientArgs;
	// }}}
	// {{{
	/**
	  * $files: array of uploaded files($_FILES)
	  *
	  * @date    08. July 2007
	  * @access  protected
	  * @var     array
	  */
	var $files;
	// }}}

	// public properties
	// {{{
	/**
	  * $argsType: type of client arguments (post or get)
	  *
	  * @date    07. July 2007
	  * @access  public
	  * @var     string
	  */
	var $argsType = "post";
	// }}}
	// {{{
	/**
	  * $contentType: Type for Content-Type header
	  *
	  * @date    07. July 2007
	  * @access  public
	  * @var     string
	  */
	var $contentType = "application/json; charset=utf-8";
	// }}}
	// {{{
	/**
	  * $rootPath: root of the tree
	  *
	  * @date    07. July 2007
	  * @access  public
	  * @var     string
	  */
	var $rootPath = "./";
	// }}}
	// {{{
	/**
	  * $dirClass: class to send to client for dirs
	  *
	  * @date    07. July 2007
	  * @access  public
	  * @var     string
	  */
	var $dirClass = "folder";
	// }}}
	// {{{
	/**
	  * $fileClass: class to send to client for files
	  *
	  * @date    07. July 2007
	  * @access  public
	  * @var     string
	  */
	var $fileClass = "file";
	// }}}
	// {{{
	/**
	  * $dirMode: Directory create mode
	  *
	  * @date    08. July 2007
	  * @access  public
	  * @var     integer
	  */
	var $dirMode = 0775;
	// }}}

	var $success;
	var $error;

	// protected methods
	// {{{
	/**
	  * output: json encodes the passed object (or this->o) and sends it to browser
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      07. July 2007
	  * @access    protected
	  * @return    void
	  * @param     object/array $o 
		* @param     string contentType
	  */
	function output($o = null, $contentType = null) {
		$o = $o ? $o : $this->o;
		$contentType = $contentType ? $contentType : $this->contentType;
		if (function_exists('json_encode')) {
			$buff = json_encode($o);
		} else {
			// For PHP 4.x
			require_once("classes/JSON.php");
			$json = new Services_JSON();
			$buff = $json->encode($o);	
		}	
		header("Content-Type: {$contentType}");
		header("Content-Size: " . strlen($buff));
		if (empty($o)) {
			$buff = '{"success":true}';
		}
		
		echo $buff;
	} // end of function output
	// }}}

	// public methods
	// {{{
	/**
	  * processClientArgs: Processes POST or GET args and put 'em into $this->clientArgs
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      07. July 2007
	  * @access    public
	  * @return    mixed Description
	  * @param     
	  */
	function processClientArgs() {
		$this->clientArgs = array();
		$srca = "post" == $this->argsType ? $_POST : $_GET;

		// process $_POST/$_GET
		foreach($srca as $k => $v) {
			$this->clientArgs[$k] = $v;
		}

		// process $_FILES
		foreach($_FILES as $k => $v) {
			$this->files[$k] = $v;
		}

		// execute command
		if(array_key_exists("cmd", $this->clientArgs)) {
			$this->docmd($this->clientArgs["cmd"]);
		}
	} // end of function processClientArgs
	// }}}
	// {{{
	/**
	  * docmd: Executes passed command
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      07. July 2007
	  * @access    public
	  * @return    double Description
	  * @param     string cmd command to execute
	  */
	function docmd($cmd) {
		$this->o = new stdClass;
		$this->o->success = false;

		// sanity check
		if(!$cmd) {
			$this->o->error = _("No command");
			$this->output();
			return;
		}
		if(!method_exists($this, $cmd)) {
			$this->o->error = sprintf(_("No such command: %s"), $cmd);
			$this->output();
			return;
		}

		$this->$cmd();

	} // end of function docmd
	// }}}
	
	function convertFileSize($inFileSize) {
		global $outFileSize;
		
		if ($inFileSize>999999) { $outFileSize = number_format( ($inFileSize/1000000),1).' MB'; }
		elseif ($inFileSize>999) { $outFileSize = number_format( ($inFileSize/1000),1).' KB'; }
		else { $outFileSize = number_format($inFileSize,1).' B';	}
		return $outFileSize;
	}
	
	// {{{
	/**
	  * get: get nodes (dirs and files) for Ext.ux.FileTree
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      07. July 2007
	  * @access    protected
	  * @return    void
	  */
	function get() {
		if(!array_key_exists("path", $this->clientArgs)) {
			$this->o->error = _("No path in get request");
			$this->output();
			return;
		}
		$path = $this->safePath($this->clientArgs["path"]);
		$fullpath = "{$this->rootPath}$path";
		$nodes = array();

		$odir = dir($fullpath);

		while(false !== ($name = $odir->read())) {
			if("." == $name) continue;
			if(".." == $name) continue;

			$fullname = "$fullpath/$name";
			if(!is_readable($fullname)) continue;

			$writable = is_writable($fullname);
			$writable = true;

			// handle dirs
			if(is_dir($fullname)) {
				$nodes[] = array("text"=>$name, "cls"=>"$this->dirClass", "disabled"=>!$writable, "leaf"=>false);
			}
			// handle files
			else {
				$atmp = explode(".", $name);
				if(1 == sizeof($atmp)) {
					$fileExtension = "";
				}
				else {
					$fileExtension = "-" . strtolower(array_pop($atmp));
				}
				$fileClass = $this->fileClass . $fileExtension;
				$size = @filesize($fullname);
				$size = $this->convertFileSize($size);
				
				$nodes[] = array("text"=>$name, "cls"=>$fileClass, "disabled"=>!$writable, "leaf"=>true, "qtip"=>_("Size") . ": " . $size);
			}
		}
		$this->o = null;
		$odir->close();
		$this->output($nodes);
	}
	// }}}
	// {{{
	/**
	  * upload: handles file uploads
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      08. July 2007
	  * @access    protected
	  * @return    void
	  */
	function upload() {
		$this->o = new stdClass;
		$o = $this->o;
		$o->success = false;
		$o->errors = array();

		$msg_perms = _("Permissions problem or disk full") . ".";

		foreach($this->files as $fieldname => $file) {
			if($file["error"]) {
				switch($file["error"]) {

					case UPLOAD_ERR_INI_SIZE: // php.ini size limit
						$o->errors[$fieldname] = _("Server file size limit exceeded") . ".";
					break;

					case UPLOAD_ERR_FORM_SIZE: // MAX_FILE_SIZE from client
						$o->errors[$fieldname] = _("Client file size limit exceeded") . ".";
					break;

					case UPLOAD_ERR_PARTIAL: // partial upload
						$o->errors[$fieldname] = _("Partial file upload") . ".";
					break;

					case UPLOAD_ERR_NO_FILE: // no file uploaded
//						$o->errors[$fieldname] = _("No file uploaded") . ".";
					break;

					case UPLOAD_ERR_NO_TMP_DIR: // no temporary directory
						$o->errors[$fieldname] = _("Temporary directory is missing") . ".";
					break;

					case UPLOAD_ERR_CANT_WRITE: // cannot write/permissions problem
						$o->errors[$fieldname] = _("Cannot write file") . "<br>" . $msg_perms;
					break;

					case UPLOAD_ERR_EXTENSION: // upload stopped by extension
						$o->errors[$fieldname] = _("Upload stopped by extension") . ".";
					break;

					default:
						$o->errors[$fieldname] = _("Unknown file upload error") . ".";
					break;
				} // end of error switch
				continue;
			}
			else {
				$path = array_key_exists("path", $this->clientArgs) ? $this->clientArgs["path"] : "";
				$path = $this->safePath($path);
				$name = $file["name"];
				$tmp_name = $file["tmp_name"];
//				error_log($tmp_name);
//				error_log("{$this->rootPath}/$path/$name");
				if(!@move_uploaded_file($tmp_name, "{$this->rootPath}/$path/$name")) {
					$o->errors[$fieldname] = _("Move uploaded file error") . ".<br>" . $msg_perms;
				}
			}
		} // end of foreach
		$o->success = 0 == sizeof($o->errors);
		$this->output(null, "text/html; charset=utf-8");

	} // end of function upload
	// }}}
	// {{{
	/**
	  * delete: handles delete of file/directory
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      08. July 2007
	  * @access    protected
	  * @return    void
	  */
	function delete() {
		$this->o = new stdClass;
		$o = $this->o;
		$o->success = false;
		if(!array_key_exists("file", $this->clientArgs)) {
			$o->error = _("No file to delete");
			$this->output();
			return;
		}
		$file = $this->safePath($this->clientArgs["file"]);
		$fullpath = "{$this->rootPath}/$file";

		// todo: write rmdir here

//		if(@unlink($fullpath)) {
		if($this->unlink($fullpath)) {
			$o->success = true;
		}
		else {
			$o->error = sprintf(_("Cannot delete file: %s"), $fullpath);
		}

		$this->output();
	}
	// }}}
	// {{{
	/**
	  * newdir: creates new directory
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      08. July 2007
	  * @access    protected
	  * @return    void
	  */
	function newdir() {
		$this->o = new stdClass;
		$o = $this->o;
		$o->success = false;
		if(!array_key_exists("dir", $this->clientArgs)) {
			$o->error = _("No directory to create");
			$this->output();
			return;
		}
		$dir = $this->clientArgs["dir"];
		$dir = $this->safePath($dir);
		$fullpath = "{$this->rootPath}/$dir";
		umask(0);
		if(@mkdir($fullpath, $this->dirMode)) {
			$o->success = true;
		}
		else {
			$o->error = sprintf(_("Cannot create directory: %s"), $fullpath);
		}

		$this->output();

	} // end of function newdir
	// }}}
	// {{{
	/**
	  * rename: renames the file of directory
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      09. July 2007
	  * @access    protected
	  * @return    void
	  */
	function rename() {
		$this->o = new stdClass;
		$o = $this->o;
		$o->success = false;

		// sanity check
		if(!array_key_exists("newname", $this->clientArgs)) {
			$o->error = _("No new name for rename");
			$this->output();
			return;
		}
		if(!array_key_exists("oldname", $this->clientArgs)) {
			$o->error = _("No old name for rename");
			$this->output();
			return;
		}
		$oldname = $this->clientArgs["oldname"];
		$oldname = $this->safePath($oldname);
		$newname = $this->clientArgs["newname"];
		$newname = $this->safePath($newname);

		$fullold = "{$this->rootPath}/{$oldname}";
		$fullnew = "{$this->rootPath}/{$newname}";

		if(@rename($fullold, $fullnew)) {
			$o->success = true;
		}
		else {
			$o->error = sprintf(_("Cannot rename file <b>%1s</b> to <b>%2s</b>"), $oldname, $newname);
		}

		$this->output();
	} // end of function rename
	// }}}
	// {{{
	/**
		* unlink: Recursive unlink function
		*
		* This will delete the provided path.  If the path is a
		* directory, it will delete the directory.  It will
		* recursively delete any subdirectories/files.
		*
		* @author    jeremiah (at) jkjonesco (dot) com
		* @see       http://www.php.net/manual/en/function.rmdir.php 
		* @date      09. July 2007
		* @access    protected
		* @return    true = success, false = failure
		* @param String $path Path to file/directory to recursively unlink
		*/
	function unlink($path) {
		/*    make sure the path exists    */
		if(!file_exists($path)) return false;
	 
		/*    If it is a file or link, just delete it    */
		if(is_file($path) || is_link($path)) return @unlink($path);
	 
		/*    Scan the dir and recursively unlink    */
		if (function_exists('scandir')) {
			$files = scandir($path);			
			foreach($files as $filename) {
				if($filename == '.' || $filename == '..') continue;
				$file = str_replace('//', '/', $path . '/' . $filename);
				self::unlink($file);
			}//end foreach
		} else {		
			$dh  = opendir($path);
			while (false !== ($filename = readdir($dh))) {
			    //$files[] = $filename;
				if($filename == '.' || $filename == '..') continue;
				$file = str_replace('//', '/', $path . '/' . $filename);				
				unlink($file);
			}
		}		
	 
		/*    Remove the parent dir    */
		if(!@rmdir($path)) return false;
		return true;
	} // end of function unlink
	// }}}
	// {{{
	/**
	  * safePath: strips . and / from the beginning of path
	  *
	  * @author    Ing. Jozef Sakáloš <jsakalos@aariadne.com>
	  * @date      18. July 2007
	  * @access    protected
	  * @return    string safe path
	  * @param     string raw path
	  */
	function safePath($path) {
		$path = (string) $path;
		$path = preg_replace(
			array(
			'#[\n\r\t\0]*#im',
			'#/(\.){1,}/#i',
			'#(\.){2,}#i',
			'#(\.){2,}#i',
			'#('.DIRECTORY_SEPARATOR.'){2,}#i'
			),
			array(
			'',
			'',
			'',
			'/'
			),
			$path
			)
		;
		return rtrim($path,DIRECTORY_SEPARATOR);
	}
	// }}}

} // end of class cfiletree

// end of file
?>
